package nl.xvr.server.web;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.Calendar;
import java.util.Date;

public class RequestHandler extends Thread {

    private String webroot;
    private String defaultpage;
    private boolean dir_browsing;
    private Socket socket;
    private WebServerLogger logger;

    public RequestHandler(String webroot, String defaultpage,
            boolean dir_browsing, Socket socket, WebServerLogger logger) {
        this.webroot = webroot;
        this.defaultpage = defaultpage;
        this.dir_browsing = dir_browsing;
        this.socket = socket;
        this.logger = logger;
    }

    public void run() {
        handle();
    }

    private void handle() {
        String logEntry = this.getLogTime() + " - Request start\n";
        try {

            BufferedReader in = new BufferedReader(new InputStreamReader(
                    socket.getInputStream()));

            // Lees de http-request-header
            String request = in.readLine();
            while (!in.readLine().equals(""))
                ; // negeer rest van request, tot en met lege regel

            String url = parseUrlFromPath(request);

            logEntry += request + "\n";
            logEntry += this.socket.getLocalAddress().getHostAddress() + "\n";

            if (url != null) {
                File f = getFileFromPath(url, (webroot + url));
                if (f != null) {
                    if (f.isDirectory()) {
                        File file = new File(f.getPath() + "/"
                                + this.defaultpage);
                        try {
                            processFile(file);
                        } catch (FileNotFoundException e) {
                            if (dir_browsing) {
                                showDirectoryBrowsing(f);
                            } else {
                                errorReport(socket, "403", "Forbidden",
                                "You don't have permission to access the requested URL.");
                            }
                        }
                    } else {
                        try {
                            processFile(f);
                        } catch (FileNotFoundException e) {
                            // file not found
                            errorReport(socket, "404", "Not Found",
                            "The requested URL was not found on this server.");
                        }
                    }
                }
            }
            socket.close();
        } catch (Exception e) {
            System.out.println("Caught Exception in RequestHandler: " + e);
        } finally {
            logEntry += this.getLogTime() + " - Request end\n";
            this.logger.log(logEntry);
        }
    }

    private String parseUrlFromPath(String request)
    throws IOException {
        String path = null;
        if (!request.startsWith("GET")
                || request.length() < 14
                || !(request.endsWith("HTTP/1.0") || request
                        .endsWith("HTTP/1.1"))) {
            // bad request
            errorReport(socket, "400", "Bad Request",
            "Your browser sent a request that this server could not understand.");
        } else {
            String req = request.substring(4, request.length() - 9).trim();
            if (req.indexOf("..") != -1 || req.indexOf("/.ht") != -1
                    || req.endsWith("~")) {
                errorReport(socket, "403", "Forbidden",
                "You don't have permission to access the requested URL.");
            } else {
                path = req;
            }
        }
        return path;
    }

    private File getFileFromPath(String url, String path) {
        File f = new File(path);
        if (f.isDirectory() && !path.endsWith("/")) {
            DataOutputStream out = null;
            try { 
                out = new DataOutputStream(socket.getOutputStream());
                // redirect browser if referring to directory without final '/'
                out.writeBytes("HTTP/1.0 301 Moved Permanently\r\n"
                        + "Location: http://"
                        + socket.getLocalAddress().getHostAddress() + ":"
                        + socket.getLocalPort() + url + "/\r\n\r\n");
                f = null;
            } catch (IOException e) {
                System.err.println(e);
            } finally {
                try {
                    out.close();
                } catch(IOException e) {

                }
            }
        }
        return f;
    }

    private String getContentType(String path) {
        if (path.endsWith(".html") || path.endsWith(".htm"))
            return "text/html";
        else if (path.endsWith(".txt") || path.endsWith(".java"))
            return "text/plain";
        else if (path.endsWith(".gif"))
            return "image/gif";
        else if (path.endsWith(".class"))
            return "application/octet-stream";
        else if (path.endsWith(".jpg") || path.endsWith(".jpeg"))
            return "image/jpeg";
        else
            return "text/plain";
    }

    private void errorReport(Socket connection, String code, String title, String msg) {
        DataOutputStream out = null;
        try { 
            out = new DataOutputStream(socket.getOutputStream());
            out.writeBytes("HTTP/1.0 " + code + " " + title + "\r\n" + "\r\n"
                    + "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
                    + "<HTML><HEAD>\r\n" + "<TITLE>" + code + " " + title
                    + "</TITLE>\r\n" + "</HEAD><BODY>\r\n" + "<H1>" + title
                    + "</H1>\r\n" + msg + "<P>\r\n"
                    + "<HR><ADDRESS>FileServer 1.0 at "
                    + connection.getLocalAddress().getHostName() + " Port "
                    + connection.getLocalPort() + "</ADDRESS>\r\n"
                    + "</BODY></HTML>\r\n");
        } catch (IOException e) {
            System.err.println(e);
        } finally {
            try {
                out.close();
            } catch(IOException e) {

            }
        }
    }

    private void processFile(File f) throws FileNotFoundException {
        // send file
        InputStream file = new FileInputStream(f);
        String header = "HTTP/1.0 200 OK\r\n" + "Content-Type: "
        + getContentType(f.getPath()) + "\r\n" + "Date: " + new Date()
        + "\r\n" + "Server: FileServer 1.0\r\n\r\n";
        sendFile(header, file); // send raw file
    }

    private void sendFile(String header, InputStream file) {
        DataOutputStream out = null;
        try { 
            out = new DataOutputStream(socket.getOutputStream());
            out.writeBytes(header);
            byte[] buffer = new byte[1000];
            while (file.available() > 0)
                out.write(buffer, 0, file.read(buffer));
        } catch (IOException e) {
            System.err.println(e);
        } finally {
            try {
                out.close();
            } catch(IOException e) {

            }
        }
    }

    private void showDirectoryBrowsing(File dir) {
        DataOutputStream out = null;
        try { 
            out = new DataOutputStream(socket.getOutputStream());
            FilenameFilter filter = new FilenameFilter() {
                public boolean accept(File dir, String name) {
                    return !name.startsWith(".");
                }
            };
            String[] children = dir.list(filter);

            out.writeBytes("HTTP/1.0 200 OK\r\n" + "Content-Type: text/html\r\n"
                    + "Date: " + new Date() + "\r\n"
                    + "Server: FileServer 1.0\r\n\r\n");
            out.writeBytes("<html>"
                    + "<head><title>Web Server - Directory Browsing</title></head>\r\n"
                    + "</body>\r\n" + "<h1>Directory Browsing</h1>\r\n"
                    + "<table width=\"450px\">\r\n");
            for (String child : children) {
                File temp = new File(dir.getPath() + "/" + child);
                if (!temp.isHidden()) {
                    out.writeBytes("<tr>\r\n" + "<td colspan=\"2\"><a href=\""
                            + temp.getName() + "\">" + temp.getName()
                            + "</a></td>\r\n");
                    if (!temp.isDirectory()) {
                        out.writeBytes("<tr>\r\n" + "<td>Last modified: "
                                + new Date(temp.lastModified()) + "</td>\r\n"
                                + "<td>Size: " + temp.length() + " Bytes</td>\r\n");

                    }
                    out.writeBytes("</tr><tr><td colspan=\"2\">&nbsp</td></tr>\r\n");
                }
            }
            out.writeBytes("</table>\r\n" + "</body>\r\n" + "</html>\r\n");
        } catch (IOException e) {
            System.err.println(e);
        } finally {
            try {
                out.close();
            } catch(IOException e) {

            }
        }
    }

    private String getLogTime() {
        return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + "-"
        + Calendar.getInstance().get(Calendar.MONTH) + "-"
        + Calendar.getInstance().get(Calendar.YEAR) + " "
        + Calendar.getInstance().get(Calendar.HOUR_OF_DAY) + ":"
        + Calendar.getInstance().get(Calendar.MINUTE) + ":"
        + Calendar.getInstance().get(Calendar.SECOND);
    }
}
